Skip to content

Add option to convert from markdown and to convert to DSL#14

Open
AutoSysOps (Leo Visser) (autosysops) wants to merge 4 commits intoPSModule:mainfrom
autosysops:main
Open

Add option to convert from markdown and to convert to DSL#14
AutoSysOps (Leo Visser) (autosysops) wants to merge 4 commits intoPSModule:mainfrom
autosysops:main

Conversation

@autosysops
Copy link
Copy Markdown

Description

In response to discussion #8 here is a PR which can read markdown files and generate a object structure out of it which is similar to the ast structure of PowerShell. There is also a function which converts this object structure to the DSL which can than be execute.

Type of change

  • 📖 [Docs]
  • 🪲 [Fix]
  • 🩹 [Patch]
  • ⚠️ [Security fix]
  • 🚀 [Feature]
  • 🌟 [Breaking change]

Checklist

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Markdown ↔ DSL conversion capabilities to the module by introducing a Markdown-to-object parser and an object-to-Markdown-DSL generator, along with documentation and Pester coverage.

Changes:

  • Add ConvertFrom-MarkdownMarkdown to parse Markdown files into a hierarchical object structure.
  • Add ConvertTo-MarkdownDSL to generate an executable DSL scriptblock (or DSL string) from the parsed object structure.
  • Add extensive Pester tests and README documentation for the new conversion workflows.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 14 comments.

File Description
tests/Markdown.Tests.ps1 Adds Pester coverage for parsing Markdown into objects, generating DSL, and executing the DSL.
src/functions/public/ConvertFrom-MarkdownMarkdown.ps1 New parser converting Markdown text into a nested object tree (headers/details/paragraphs/codeblocks/tables).
src/functions/public/ConvertTo-MarkdownDSL.ps1 New generator that emits DSL text/scriptblocks from the parsed object structure.
README.md Documents the new conversion functions and a round-trip workflow.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md
Comment thread src/functions/public/ConvertFrom-MarkdownMarkdown.ps1 Outdated
Comment thread src/functions/public/ConvertFrom-MarkdownMarkdown.ps1
Comment thread src/functions/public/ConvertTo-MarkdownDSL.ps1
Comment thread src/functions/public/ConvertTo-MarkdownDSL.ps1
Comment thread src/functions/public/ConvertFrom-MarkdownMarkdown.ps1 Outdated
Comment thread src/functions/public/ConvertFrom-MarkdownMarkdown.ps1 Outdated
Comment thread src/functions/public/ConvertTo-MarkdownDSL.ps1 Outdated
Comment thread src/functions/public/ConvertFrom-MarkdownMarkdown.ps1 Outdated
Comment thread src/functions/public/ConvertTo-MarkdownDSL.ps1
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 9, 2026 17:34
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed copilot review

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 9 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +151 to +155
$markDownString += "[PSCustomObject]@{"
foreach ($property in $item.PSObject.Properties) {
$markDownString += "'$($property.Name)' = `'$($property.Value)`'; "
}
$markDownString += "}$newline"
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Table row serialization writes '$property.Value' into PowerShell source without escaping embedded single quotes/newlines. A value like O'Reilly will generate invalid DSL, and crafted values could break out of the literal context. Escape table values for single-quoted PowerShell strings (double any ') before writing them into $markDownString.

Copilot uses AI. Check for mistakes.
[Switch] $DontQuoteString
)

# First the file is converted to a string containing the syntaxt for the markdown file then it will be invoked to be converted by the other functions.
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: “syntaxt” should be “syntax”.

Copilot uses AI. Check for mistakes.
Comment on lines +96 to +97
# Loop through all childeren of the input object
foreach ($child in $InputObject) {
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: “childeren” should be “children”.

Copilot uses AI. Check for mistakes.
Comment on lines +146 to +153
# Check if the word starts with ```
if ($word -match '^```') {
if ($inCodeBlock) {
Write-Debug "[CodeBlock] End found, returning to parent"
# Go to the parent
$currentObject = [ref]$currentObject.Value.Parent

$inCodeBlock = $false
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When $inCodeBlock is true, the parser still evaluates table/header/HTML logic for each line before it reaches this fence handler. As a result, code lines beginning with | or # can be mis-parsed as tables/headers instead of being kept as code content. Consider adding an early check near the start of the loop: if $inCodeBlock and the line is not a closing ``` fence, append the raw line to the current CodeBlock content and continue.

Copilot uses AI. Check for mistakes.
Comment on lines +102 to +106
$markDownString += "$($child.Type) "
if ($child.Level) { $markDownString += "$($child.Level) " }
if ($child.Title) { $markDownString += "`"$($child.Title)`" " }
if ($child.Language) { $markDownString += "`"$($child.Language)`" " }
$markDownString += "{$newline"
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Title/Language are injected into the generated PowerShell source using double-quoted string literals without escaping. This breaks for values containing "/backticks/newlines and also enables PowerShell expansion ($var, $()) when the generated DSL is executed, which can change content or be abused if the Markdown is untrusted. Use a safe quoting/escaping strategy for PowerShell literals (typically single-quoted strings with ' doubled, or a dedicated escaping helper) before concatenating into $markDownString.

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +80
[OutputType([scriptblock], [string])]
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 0)]
[Object[]] $InputObject,

[Parameter(Mandatory = $false, Position = 1)]
[Switch] $AsString,

[Parameter(Mandatory = $false, Position = 2)]
[Switch] $DontQuoteString
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[OutputType([scriptblock])] is inaccurate here since -AsString returns a [string]. This can mislead tooling and documentation generation. Consider declaring both output types (string + scriptblock) or adjusting the attribute to reflect the conditional output.

Copilot uses AI. Check for mistakes.
Comment thread tests/Markdown.Tests.ps1
Comment on lines +255 to +256
$testFile = Join-Path ([System.IO.Path]::GetTempPath()) "PesterMarkdownTest_$([guid]::NewGuid().ToString()).md"
@'
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests rely on $env:TEMP being set. On some PowerShell environments (especially non-Windows), TEMP may be unset or point somewhere unexpected, which can make the tests flaky. Consider using [System.IO.Path]::GetTempPath(), New-TemporaryFile, or Pester’s temp location patterns for portable test files.

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +40
# Create a variable to hold the current working object in the converstion and helper variables
$currentObject = [ref]$returnObject
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: “converstion” should be “conversion”.

Copilot uses AI. Check for mistakes.
Comment on lines +168 to +176
# return the right value
if ($AsString) {
Write-Debug "[Output] Returning as string"
return $markDownString
}
else {
Write-Debug "[Output] Returning as scriptblock"
return [scriptblock]::Create($markDownString)
}
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConvertTo-MarkdownDSL builds a PowerShell DSL script string from InputObject fields (e.g., Title, free‑text content, table cell values) and then passes it directly to [scriptblock]::Create, without escaping quotes or preventing PowerShell interpolation. When InputObject originates from ConvertFrom-MarkdownMarkdown reading a Markdown file, an attacker can craft headings, paragraphs, or table cells containing PowerShell syntax (e.g., $() subexpressions or embedded quotes/semicolons) so that arbitrary commands execute whenever the generated scriptblock is invoked. To mitigate this, treat Markdown‑derived data as pure data by rigorously escaping/encoding all interpolated values (including quotes and $), disabling DontQuoteString for untrusted input, or avoiding scriptblock creation entirely in favor of interpreting the object tree directly.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants